package com.ratelimit.redis.redisson;

import crazymakercircle.util.RandomUtil;
import lombok.extern.slf4j.Slf4j;
import org.redisson.Redisson;
import org.redisson.api.RLock;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.types.RedisClientInfo;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

/**
 * 如果k-v都是String类型，我们可以直接用 StringRedisTemplate，这个是官方建议的，也是最方便的，直接导入即用，无需多余配置！
 * 如果k-v是Object类型，则需要自定义 RedisTemplate，在这里我们都研究下！
 */
@RestController
@Slf4j
public class IndexController {

    @Autowired
    private StringRedisTemplate stringRedisTemplate;


    @Autowired
    private RedisTemplate redisTemplate;

    @Resource
    RedisConnectionFactory redisConnectionFactory;


    @Autowired
    Redisson redisson;


    /**
     * 使用Redission实现一个分布式锁
     */

    @RequestMapping(value = "/redission")
    public String redissionStock() {

        String lockKey = "lockKey";
        RLock lock = redisson.getLock(lockKey);
        try {
            lock.lock();
            return createAssign();
        } finally {
            lock.unlock();
        }


    }


    /**
     * 使用Reission实现分布式锁
     */
    /**
     * 自己实现一个分布式锁：
     * <p>
     * 分布式锁写法注意点：
     * 1.加锁一定要有失效时间
     * 2.我自己加的锁，被别人释放了，面对这一种情况，需要保证自己删除自己的锁，使用全局唯一UUID
     * 3.为了防止死锁，一定要保证代码被try catch  finally进行包裹，保证代码执行完释放锁，即使有意外也要释放锁
     * 4.锁续命问题：如果执行过程中没有结束，锁不能过期
     */
    @RequestMapping(value = "/customer")
    public String customerStock() {

        String lockKey = "lockKey";

        String uuid = UUID.randomUUID().toString();

        //下面设置key一定要原子性  setIfAbsent是原子性命令
        Boolean aBoolean = stringRedisTemplate.opsForValue().setIfAbsent(lockKey, uuid, 10, TimeUnit.SECONDS);//jedis.setnx(k,v)
        List<RedisClientInfo> clientList = stringRedisTemplate.getClientList();

        if (!aBoolean) {
            return "error_code";
        }

        try {
            return createAssign();
        } finally {

            //下面判断和删除一定要原子性操作
            if (uuid.equals(stringRedisTemplate.opsForValue().get(lockKey))) {

                //这里执行了9.99s  然后GC卡顿，导致超过10S才删除，结果Redis已经自己删除掉了。
                stringRedisTemplate.delete(lockKey);
            }
        }


    }

    private String createAssign() {
        synchronized (this) {
            int stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("stock"));
            if (stock > 0) {
                int realStock = stock - 1;
                stringRedisTemplate.opsForValue().set("stock", realStock + "");
                log.info("扣减成功，剩余库存：" + realStock);
            } else {
                log.info("库存不足");
            }
        }
        return "end";
    }

}
